namespace Minimal.Web
{
    using System;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Security.Permissions;
    using System.Web.UI;
    using Legend.Web.QueryString; 
    
    /// <summary>
    /// Provides a base class for strongly typed query strings.
    /// The query strings can also optionally be encrypted.
    /// </summary>
    [ReflectionPermission(SecurityAction.Demand)]
    public abstract partial class QueryString
        : ICloneable, IRedirectManager
    {
        private IRedirectManager redirectManager;
        
        /// <summary>
        /// Creates a new instance.
        /// </summary>
        protected QueryString()
        {
            this._encryptQueryString = QueryStringConfiguration.Instance.EncryptQueryString;
            this._includeMetadata = QueryStringConfiguration.Instance.IncludeMetadata;
            this.redirectManager = this;
        }
        
        private bool _encryptQueryString;
        
        /// <summary>
        /// If set to true the query string will be encrypted when rendered.
        /// </summary>
        public bool EncryptQueryString
        {
            get
            {
                return this._encryptQueryString;
            }
            set
            {
                this._encryptQueryString = value;
            }
        }
        
        [DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
        private bool _includeMetadata;

        /// <summary>
        /// Whether metadata should be included in the rendered query string.
        /// </summary>
        public bool IncludeMetadata
        {
            [DebuggerStepThroughAttribute]
            get
            {
                return this._includeMetadata;
            }
            [DebuggerStepThroughAttribute]
            set
            {
                this._includeMetadata = value;
            }
        }
        

        
        /// <summary>
        /// The key to use for the encrypted query string value if
        /// encryption of the query string is turned on.
        /// </summary>
        private static string EncryptedQueryStringKey
        {
            [DebuggerStepThrough]
            get
            {
                return QueryStringConfiguration.Instance.EncryptedQueryStringKey;
            }
        } 
        

        
        /// <summary>
        /// Key to use when encrypting and decrypting query strings.
        /// </summary>
        private static string EncryptionKey
        {
            get
            {
                return QueryStringConfiguration.Instance.EncryptionKey;
            }
        }
        

        
        [DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
        private Page _page;

        /// <summary>
        /// The page that this QueryString belongs to.
        /// </summary>
        public Page Page
        {
            [DebuggerStepThroughAttribute]
            get
            {
                return this._page;
            }
            [DebuggerStepThroughAttribute]
            set
            {
                this._page = value;
            }
        }
        

        
        /// <summary>
        /// A key to use for the query string type metadata in the query string.
        /// </summary>
        private static string MetadataKey
        {
            get
            {
                return QueryStringConfiguration.Instance.MetadataKey;
            }
        }
        
        /// <summary>
        /// Redirects to the page specified in the url parameter.
        /// </summary>
        /// <param name="url">The url to redirect to, relative or static url's are
        /// accepted.</param>
        /// <exception cref="NotSupportedException">The Control-property is not set.</exception>
        /// <exception cref="ArgumentException">An invalid url has been specified.</exception>
        [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads",
            Justification="The url my be relative therefore it can not be converted to the Uri-type.")]
        public void Redirect(string url)
        {
            if (string.IsNullOrEmpty(url))
            {
                throw new ArgumentException(QueryStringResources.ToStringEmptyUrlException);
            }

            this.DoRedirect(url);
        }

        /// <summary>
        /// Performs the redirect to the specified url.
        /// </summary>
        /// <param name="url">The url to redirect to.</param>
        private void DoRedirect(string url)
        {
            this.redirectManager.Redirect(this.ToString(url));
        }

        /// <summary>
        /// Redirects to the page specified in the url parameter.
        /// </summary>
        /// <param name="url">The url to redirect to.</param>
        /// <exception cref="NotSupportedException">The Control-property is not set.</exception>
        /// <exception cref="ArgumentException">An invalid url has been specified.</exception>
        public void Redirect(Uri url)
        {
            if (url == null)
            {
                throw new ArgumentNullException("url");
            }

            this.DoRedirect(url.AbsoluteUri);
        }
        

        
        /// <summary>
        /// Creates a string representation of the query string.
        /// </summary>
        /// <returns>A query string.</returns>
        public override string ToString()
        {
            QueryStringManager manager = null;

            if (this.EncryptQueryString)
            {
                manager = CreateManagerForEncryptedQueryStrings();
            }
            else
            {
                manager = CreateManagerForNonEncryptedQueryStrings();
            }

            return manager.Render(this);
        }

        /// <summary>
        /// Creates the query string and appends it to the given url.
        /// </summary>
        /// <param name="url">The url to append the query to, any existing query will be replaced.</param>
        /// <returns>The complete url including this query string.</returns>
        [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", 
            Justification="The url may be relative, therefore it can not be conveterd to the Uri-type.")]
        public string ToString(string url)
        {
            if (string.IsNullOrEmpty(url))
            {
                throw new ArgumentException(QueryStringResources.ToStringEmptyUrlException, "url");
            }

            return QueryString.CombineUrlAndQuery(url, this.ToString());
        }

        /// <summary>
        /// Creates the query string and appends it to the given url.
        /// </summary>
        /// <param name="url">The url to append the query to, any existing query will be replaced.</param>
        /// <returns>The complete url including this query string.</returns>
        public string ToString(Uri url)
        {
            if (url == null)
            {
                throw new ArgumentNullException("url");
            }

            return QueryString.CombineUrlAndQuery(url.AbsoluteUri, this.ToString());
        }
        

        
        private static string CombineUrlAndQuery(string url, string query)
        {
            return (string.IsNullOrEmpty(query)) ? url : url + '?' + query;
        }
        

        
        /// <summary>
        /// Parse the supplied query string into a QueryString object.
        /// </summary>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <param name="queryString">The query string to parse.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(string queryString) where T : QueryString, new()
        {
            return QueryString.Parse<T>(queryString, null);
        }

        /// <summary>
        /// Parse the supplied query string into a QueryString object.
        /// </summary>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="control">The control or page the query string belongs to.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(string queryString, Control control) where T : QueryString, new()
        {
            T result = new T();
            if (control != null)
            {
                result.Page = control.Page;
            }
            QueryString.PrivateParse(queryString, result);
            return result;
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object.
        /// </summary>
        /// <param name="queryString">The query string to parse in the form used
        /// by the Request.QueryString-property.</param>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(NameValueCollection queryString) where T : QueryString, new()
        {
            return QueryString.Parse<T>(queryString, null);
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object.
        /// </summary>
        /// <param name="queryString">The query string to parse in the form used
        /// by the Request.QueryString-property.</param>
        /// <param name="control">The Page or Control the query string belongs to.</param>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(NameValueCollection queryString, Control control) where T : QueryString, new()
        {
            T result = new T();
            if (control != null)
            {
                result.Page = control.Page;
            }
            QueryString.PrivateParse(queryString, result);
            return result;
        }
        

        
        /// <summary>
        /// Parses the specified query string applying its values to the QueryString
        /// specified in the <paramref name="result"/> parameter.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="result">The QueryString to apply the parsed values to.</param>
        private static void PrivateParse(NameValueCollection queryString, QueryString result)
        {
            bool isEncrypted = QueryString.IsEncrypted(queryString);

            QueryStringManager manager = null;

            if (isEncrypted)
            {
                manager = CreateManagerForEncryptedQueryStrings();
            }
            else
            {
                manager = CreateManagerForNonEncryptedQueryStrings();
            }

            manager.ApplyQueryStringValuesToQueryObject(queryString, result);
        }

        private static QueryStringManager CreateManagerForEncryptedQueryStrings()
        {
            return new QueryStringManager(new EncryptedQueryStringSerializer(EncryptionKey));
        }

        private static QueryStringManager CreateManagerForNonEncryptedQueryStrings()
        {
            return new QueryStringManager(new QueryStringSerializer());
        }

        /// <summary>
        /// Parses the specified query string, applying the values to the
        /// <paramref name="result"/> <see cref="QueryString" />-instance.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="result">The instance that the parsed values should be
        /// applied to.</param>
        private static void PrivateParse(string queryString, QueryString result)
        {
            bool isEncrypted = QueryString.IsEncrypted(queryString);

            QueryStringManager manager = null;

            if (isEncrypted)
            {
                manager = CreateManagerForEncryptedQueryStrings();
            }
            else
            {
                manager = CreateManagerForNonEncryptedQueryStrings();
            }

            manager.ApplyQueryStringValuesToQueryObject(queryString, result);
        }
        

        
        /// <summary>
        /// Gets if the specified query string has been encrypted or not.
        /// </summary>
        /// <param name="queryString">The query string to test.</param>
        /// <returns>True if the query string was encrypted.</returns>
        private static bool IsEncrypted(string queryString)
        {
            return string.IsNullOrEmpty(queryString) || 
                queryString.StartsWith(QueryString.EncryptedQueryStringKey);
        }

        /// <summary>
        /// Gets if the specified query string has been encrypted or not.
        /// </summary>
        /// <param name="queryString">The query string to test.</param>
        /// <returns>True if the query string was encrypted.</returns>
        private static bool IsEncrypted(NameValueCollection queryString)
        {
            return queryString[QueryString.EncryptedQueryStringKey] != null;
        }
        
        object ICloneable.Clone()
        {
            return this.Clone();
        }

        /// <summary>
        /// Creates a new instance with the same values as this instance.
        /// </summary>
        /// <returns>A clone of this instance.</returns>
        public QueryString Clone()
        {
            QueryString result = (QueryString)Activator.CreateInstance(this.GetType());
            result.EncryptQueryString = this.EncryptQueryString;
            result.IncludeMetadata = this.IncludeMetadata;
            result.Page = this.Page;
            
            var myParameters = new AttributedParameterAccessor(this);
            var resultsParameters = new AttributedParameterAccessor(result);

            foreach (var parameter in myParameters.GetAllParametersAndValues())
            {
                resultsParameters.ApplyParameterValue(parameter.Key, parameter.Value);
            }

            return result;
        }
        
        /// <summary>
        /// Redirects to the specified uri.
        /// </summary>
        /// <param name="relativeUrl">The url to redirect to, may be an absolute url, a relative
        /// url or a virtual path.</param>
        void IRedirectManager.Redirect(string relativeUrl)
        {
            if (this.Page == null)
            {
                throw new NotSupportedException(QueryStringResources.RedirectWithNoPageException);
            }

            try
            {
                this.Page.Response.Redirect(relativeUrl, true);
            }
            catch (System.Threading.ThreadAbortException) { }
        } 
        
        
    }
}
